var Config = {};
/* domready events { */
function $domready(func, priority) {
	if (!$domready._list) $domready._list = [];
	if ($domready._loaded) func.delay(1);
	if (priority === undefined) priority = 5;
	$domready._list.push({ func: func, priority: priority });
}
$domready.loadAll = function() {
	$domready._loaded = true;
	if (!$domready._list) return;

	$domready._list.sort(function(a, b) {
		return a.priority - b.priority;
	});

	$domready._list.each(function(o) {
		o.func.call(window);
	});
	delete $domready._list;
};

function $DL() { $domready.apply(this, arguments); }

window.addEvent("domready", $domready.loadAll);
/* } domready events */

function addNamespace(ns) {
	if (!ns) return null;
	var levels = ns.split(".");
	var root = window;
	for (var i = 0; i < levels.length; i++) {
		if (root[levels[i]] == undefined) root[levels[i]] = {};
		root = root[levels[i]];
	}
	return root;
}

// TODO: make a function to add key and get new url without redirecting
// TODO: inherit Hash?
var QueryManager = new Class({
	data: null,
	// data - a key value collection
	initialize: function(data) {
		this.data = data;
	},
	add: function(keyValueCollection) {
		if (!(keyValueCollection instanceof Hash)) keyValueCollection = new Hash(keyValueCollection);

		this.data.extend(keyValueCollection);

		this.data.each(function(value, key) {
			if (key === null) this.data.erase(key);
		}, this);

		this._redirect();
	},
	remove: function(names) {
		for (var i = 0; i < names.length; i++) this.data.erase(names[i]);
	},
	get: function(key) {
		return this.data.get(key);
	},
	_redirect: function() {
		location.href = this.getUrl();
	},
	// abstract function
	getUrl: function() { throw new Error("not implemented"); }
});

var QueryStringManager = new Class({
	Extends: QueryManager,

	initialize: function() {
		var data = new Hash();
		location.search.substr(1).replace(/(.*?)=(.*?)(?:&|$)/g, function(match, key, value) {
			data.set(key, decodeURIComponent(value));
		});
		this.parent(data);
	},
	remove: function(names) {
		this.parent(names);
		return this;
	},
	getUrl: function() {
		return "?" + this.data.toQueryString();
	}
});

/* UrlQuery Manager */
var UrlQueryManager = new Class({
	Extends: QueryManager,

	initialize: function() {
		var data = new Hash();
		if (UrlQueryManager.query) UrlQueryManager.query.replace(/(.*?)(?:=(.*?))?(?:&|\/|$)/g, function(match, key, value) {
			if (key) data.set(key, value ? decodeURIComponent(value) : null);
		});
		this.parent(data);
	},
	get: function(key) {
		if (typeof (key) == "number") return this.data.getKeys()[key];
		return this.parent(key);
	},
	getUrl: function() {
		var extLess = UrlQueryManager.useExtensionlessUrlRewriting;
		var qsPrefix = extLess ? "/" : "?",
			delimeter = extLess ? "/" : "&";

		var urlQuery = [];
		this.data.each(function(value, key) {
			$splat(value).each(function(val) {
				if (!val && extLess) urlQuery.push(key); // only key
				else urlQuery.push(key + "=" + encodeURIComponent(val)); // key and value
			});
		});

		return UrlQueryManager.pageUrl + qsPrefix + urlQuery.join(delimeter);
	}
});

// UQ.add({a:1})

var QS, UQ;
$domready(function() {
	QS = new QueryStringManager();
	UQ = new UrlQueryManager();
}, 2);

function h(o, funcs, showFuncContent) {
	if (o == null) return "[null]";
	var s = [];
	for (var i in o) {
		switch ($type(o[i])) {
			case "function":
				if (funcs) s.push(i + "\t\t" + (showFuncContent ? o[i] : o[i].toString().trim().substr(0, o[i].toString().search(/\r|\n|\{/))));
				break;
			case "array": s.push(i + "\t\t[" + o[i].length + "]");
				break;
			default: s.push(i + "\t\t" + o[i]);
		}
	}
	return "\r\n" + s.join("\r\n") + "\r\n";
}
function r() { alert($A(arguments).join("\n")); };


// default failure for ajax requests
function $ajaxFailure(xh) {
	//r.apply(null,arguments);
	var err = "";
	if (/<title>(.*?)<\/title>/.test(xh.responseText)) err = RegExp.$1;
	err = err.replace(/<.*?>/g, "");
	alert("Ajax Error: " + err);
}

function getAjaxDate(dateString) {
	var date = null;
	if (/Date\((\d+)\)/.test(dateString)) date = new Date(+RegExp.$1);
	return date;
}

function $waitUntil(waitUntil, onComplete, delay, timeout) {
	switch ($type(waitUntil)) {
		case "string":
			if (waitUntil.contains(",")) waitUntil = waitUntil.split(",");
			else waitUntil = [waitUntil];
			$waitUntil(waitUntil, onComplete, delay);
			return;
		case "array":
			var variableNames = waitUntil;
			waitUntil = function() {
				// check for all types, if one of them is undefined, return false
				for (var i = 0; i < variableNames.length; i++) {
					var name = variableNames[i];
					if (name.contains(".")) {
						var parts = name.split(".");
						var temp = window;
						// if one of the parts of the object is still undefined, return false
						for (var j = 0; j < parts.length; j++) {
							temp = temp[parts[j]];
							if (!temp) return false;
						}
					}
					if (window[name] == undefined) return false;
				}
				return true;
			};
			$waitUntil(waitUntil, onComplete, delay);
			return;
		case "function":
			break;
		default:
			throw new Error("Argument " + waitUntil + " is invalid for $waitUntil");
	}

	if (waitUntil()) {
		onComplete();
		return;
	}

	var timeoutPointer;
	var intervalPointer = function() {
		if (!waitUntil()) return;
		intervalPointer = $clear(intervalPointer);
		if (timeoutPointer) timeoutPointer = $clear(timeoutPointer);
		onComplete();
	} .periodical(delay || 100);
	// if after timeout ms function doesn't return true, abort
	if (timeout) timeoutPointer = function() {
		dbug.log("$waitUntil timed out.", waitUntil);
		intervalPointer = $clear(intervalPointer);
	} .delay(timeout);
}

/*
var glob=0;

$waitUntil(
function () {
console.log('glob');
return glob==1;
},
function () {
alert('glob!');
},
100
);
(function () { glob=1; }).delay(2000);
*/

// all external links opened in new window by default
// if you want to override this default behavior, put event on a link and stop propagation
/*$domready(function () {
$(document.body).delegateEvent("click","a",function (e) {
if (this.rel=="External" || this.hasClass("external") || !this.href.indexOf(Config.siteUrl)==0) {
open(this.href);
e.stop();
}
});
});*/

/* fix selects and labels on ie6 */
if (Browser.Engine.trident4) {
	$$("select").addEvents({
		focusin: function(e) { this.store("tempIndex", this.selectedIndex); },
		focus: function(e) { this.selectedIndex = this.retrieve("tempIndex"); }
	});
}

/*<debug>*/
(function() {
	if (!Browser.Engine.gecko) return;
	var _emptyTags = ["img", "br", "input", "meta", "link", "param", "hr"];
	HTMLElement.prototype.__defineGetter__("outerHTML", function() {
		var attrs = this.attributes;
		var tag = this.tagName.toLowerCase();
		var str = "<" + tag;
		for (var i = 0; i < attrs.length; i++) str += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
		if (_emptyTags.indexOf(tag)) return str + "/>";
		return str + ">" + this.innerHTML + "</" + tag + ">";
	});
})();
/*</debug>*/

function $returnFalse() { return false; }

Function.implement({
	callOnce: function(bind) {
		if (!this.$alreadyCalled) this.call(bind);
		this.$alreadyCalled = true;
	},
	resetCall: function() {
		this.$alreadyCalled = null;
	},
	// use -- Math.cos.memoize(inValue)
	memoize: function() {
		var args = $A(arguments);
		return ((this.memoized || (this.memoized = {}))[args] || (this.memoized[args] = this.apply(this, args)));
	}
});

Array.implement({
	pluck: function(property) {
		var a = [];
		this.each(function(o) { a.push(o ? o[property] : null); });
		return a;
	},
	invoke: function(fn, args) {
		var result = [];
		for (var i = 0, l = this.length; i < l; i++) {
			if (this[i] && this[i][fn]) result.push(args ? this[i][fn].pass(args, this[i])() : this[i][fn]());
		}
		return result;
	}
});

//Element.prototype.$oldEmpty=Element.prototype.empty;

Window.implement({
	$E: function(selector) {
		return this.document.getElement(selector);
	},
	$ES: function(selector) {
		return this.document.getElements(selector);
	}
});
Element.implement({
	setHTML: function(callback) {
		var html = Array.join(arguments, "");

		html = html.trim().stripScripts(true);

		var tag = this.get("tag");
		var tableIdx = -1;
		switch (tag) {
			case "table":
				html = "<table>" + html + "</table>";
				tableIdx = 1;
				break;
			case "tbody": case "thead": case "tfoot":
				html = "<table><" + tag + ">" + html + "</" + tag + "></table>";
				tableIdx = 2;
				break;
			case "tr":
				html = "<table><tbody><tr>" + html + "</tr></tbody></table>";
				tableIdx = 3;
				break;
		}

		if (tableIdx > -1) {
			var div = new Element("div").set("html", html);
			var child = div;
			for (var i = 0; i < tableIdx; i++) child = child.firstChild;
			this.removeChildren().adopt(child.childNodes);
		}
		else this.innerHTML = html;
		return this;
	},
	setVisibility: function(visible) {
		return this.set("visibility", visible);
	},
	visible: function() {
		return this.get("visibility");
	},

	toggle: function() {
		this.set("display", "toggle");
	},

	hover: function(over, out) {
		return this.addEvent("mouseenter", over).addEvent("mouseleave", out);
	},

	setSelection: function(b) {
		if (Browser.engine.trident || Browser.engine.presto) this[(b ? "remove" : "add") + "Event"]("selectstart", $returnFalse);
		if (Browser.engine.gecko) this.style.MozUserSelect = b ? "" : "none";
		if (Browser.engine.webkit) this.style.KhtmlUserSelect = b ? "" : "none";
		return this;
	},

	removeTextNodes: function() {
		if (this._removedTextNodes) return;
		$A(this.childNodes).each(function(o) {
			if (o.nodeType === 3) {
				if (/^\s+$/.test(o.data)) o.parentNode.removeChild(o);
			}
			else $(o).removeTextNodes();
		});
		this._removedTextNodes = true;
		return this;
	},

	removeChildren: function() {
		while (this.hasChildNodes()) this.removeChild(this.lastChild);
		return this;
	},

	retrieveOrStore: function(key, getValueFunction) {
		if (!this.retrieve(key)) this.store(key, getValueFunction.call(this));
		return this.retrieve(key);
	},

	addReplacingEvent: function(type, fn) {
		return this.addEvent(type, function(e) {
			e.stop();
			fn.call(this, e);
		});
	},
	effects: function(options) {
		return new Fx.Morph(this, options);
	},
	effect: function(property, options) {
		options = $extend({ property: property }, options);
		return new Fx.Tween(this, options);
	},

	/*
	Event delegation - instead of attaching events to new elements, attach to parent and use bubbling to fire children events

	Useage:

	<ul id="list">
	<li>Foo</li>
	<li>Bar</li>
	<li>Bar</li>
	</ul>

	$("list").delegateEvent("click","li",function(e) { this==li },false,true);
	// or
	$("list").delegateEvent("click",function (el) { return Element.get(el,'tag')=="li"; },function(e) { this==li },false,true);
	*/
	delegateEvent: function(type, selector, fn, preventDefault, stopPropagation) {
		var check = $type(selector) == "function" ? selector : function(target) { return Element.match(target, selector); };
		return this.addEvent(type, function(e) {
			var target = e.target;
			while (target != this && target != null) {
				if (check(target)) {
					if (preventDefault) e.preventDefault();
					if (stopPropagation) e.stopPropagation();
					return fn.apply($(target), [e]);
				}
				target = target.parentNode;
			}
		} .bind(this));
	},

	getThisOrParent: function(selector) {
		return Element.match(this, selector) ? this : this.getParent(selector);
	},
	getThisOrChild: function(selector) {
		return Element.match(this, selector) ? this : this.getElement(selector);
	},
	show: function() {
		return this.set("display", true);
	},
	hide: function() {
		return this.set("display", false);
	},
	toQueryString: function() {
		var queryString = [];
		this.getElements('input, select, textarea').each(function(el) {
			if (!el.name || el.disabled) return;
			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt) {
				return opt.value;
			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
			$splat(value).each(function(val) {
				queryString.push(el.name + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	}
});

Element.Properties.extend({
	visibility: {
		set: function(visible) {
			if (visible === "toggle") {
				this.set("visibility", !this.get("visibility"));
				return;
			}
			if (visible) this.removeClass("visibility-hidden").setStyle("visibility", "");
			else this.addClass("visibility-hidden").setStyle("visibility", "hidden");
		},
		get: function() {
			// check all parents for one that's not visible
			var p = this;
			while (p && p.get("tag") != "body" && p.getStyle("display") != "none" && p.getStyle("visibility") != "hidden") {
				p = p.getParent();
			}
			return p && p.get("tag") == "body";
		}
	},
	display: {
		set: function(visible) {
			if (visible === "toggle") {
				this.set("display", !this.get("display"));
				return;
			}
			if (visible) this.removeClass("display-none").setStyle("display", "");
			else this.addClass("display-none").setStyle("display", "none");
		},
		get: function() {
			return this.get("visibility");
		}
	}
});

Element.fromMarkup = function(markup, multipleElements) {
	markup = markup ? markup.trim() : "";
	var div = new Element("div").setHTML(markup);
	if (multipleElements) return div.getChildren();
	else {
		if (div.childNodes.length > 1) return div;
		else return div.getFirst();
	}
};

// TODO ticket
$extend(String.prototype, {
	/* override native stripScripts, added externalScripts support */
	stripScripts: function(option) {
		var srcRx = /\ssrc=('|"|)(.*?)\1/gi;
		var scripts = '';
		var src;
		var externalScripts = [];
		var text = this.replace(/\s*<script([^>]*?)>([\s\S]*?)<\/script>\s*/gi, function(all, attrs, content) {
			if (src = srcRx.exec(attrs)) externalScripts.push(src[2]);
			else scripts += content + '\n';
			return '';
		});

		if (option === true) {
			$exec(scripts);
			externalScripts.each(Asset.javascript);
		}
		else if ($type(option) == 'function') option(scripts, text);

		return text.trim();
	}
});
$extend(Element.prototype, {
	clone: function(contents, keepid) {
		switch ($type(this)) {
			case 'element':
				var attributes = {};
				for (var j = 0, l = this.attributes.length; j < l; j++) {
					var attribute = this.attributes[j], key = attribute.nodeName.toLowerCase();
					if (Browser.Engine.trident && (/input/i).test(this.tagName) && (/width|height/).test(key)) continue;
					var value = (key == 'style' && this.style) ? this.style.cssText : attribute.nodeValue;
					if (!$chk(value) || key == 'uid' || (key == 'id' && !keepid)) continue;
					if (value != 'inherit' && ['string', 'number'].contains($type(value))) attributes[key] = value;
				}
				var element = new Element(this.nodeName.toLowerCase(), attributes);
				if (contents !== false) {
					for (var i = 0, k = this.childNodes.length; i < k; i++) {
						if (/script/i.test(this.childNodes[i].tagName)) continue; // this is the change

						var child = Element.clone(this.childNodes[i], true, keepid);
						if (child) element.grab(child);
					}
				}
				return element;
			case 'textnode': return document.newTextNode(this.nodeValue);
		}
		return null;
	}
});
function $clone(o) {
	if (typeof (o) != "object") return o;
	if (o == null) return o;
	var newO = {};
	for (var i in o) newO[i] = $clone(o[i]);
	return newO;
}

String.implement({
	escapeHtml: function(isHtml) {
		var str = this;
		if (!isHtml) str = str.replace(/>/g, "&gt;").replace(/</g, "&lt;");
		else str = str.replace(/\r?\n/g, "<br/>");
		str = str.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
		return str;
	}
});

Events.makeObjectEventable = function(obj) {
	$extend(obj, Events.prototype);
};
Options.makeObjectOptionable = function(obj) {
	$extend(obj, Options.prototype);
};
Options.makeClassOptionable = function(cls) {
	cls.setOptions = function() {
		Options.prototype.setOptions.apply(cls.prototype, arguments);
	};
};

$(document.body);

// web service class to communicate with .asmx files with json
var WebService = new Class({
	Implements: [Chain, Options, Events],
	request: null,
	initialize: function(options) {
		this.setOptions(options);
	},
	send: function(methodName, data, onSuccess, onFailure) {
		this.request = new Request.JSON({
			url: this.url + "/" + methodName,
			onSuccess: function(response) {
				// .d property is of .net 3.5 stuff
				if ($type(response) == "object" && response.d !== undefined) response = response.d;
				if (onSuccess) onSuccess.call(this, response);
			},
			onFailure: function(ex) {
				var error = eval("(" + ex.responseText + ")").Message;
				if (onFailure) onFailure.call(this, error);
				else alert(error);
			} .bind(this),
			data: JSON.encode(data || {}),
			urlEncoded: false
		});
		this.request.headers
			.erase("Accept")
			.erase("X-Request")
			.extend({ "Content-Type": "application/json; charset=utf-8" });

		WebService.fireEvent("onSending", [this.request, methodName]);
		this.fireEvent("onSending", [this.request, methodName]);
		this.request.send();
		this.fireEvent("onSent", [this.request, methodName]);
		WebService.fireEvent("onSent", [this.request, methodName]);

		return this;
	},
	abort: function() {
		if (this.request) this.request.cancel();

		return this;
	}
});
Events.makeObjectEventable(WebService);
/*
WebService.addEvents({
onSending:function (request,methodName) {
request.headers.extend({"I-Am-A":"Custom Header"});
request.options.url="i-am-a-custom.url";
},
onSent:function (request,methodName) {
}
});
*/
$domready(function() {
	WebService.AuthenticationServiceClass = new Class({
		Extends: WebService,
		url: Config.rootUrl + "Authentication_JSON_AppService.axd",
		login: function(userName, password, createPersistentCookie, onSuccess, onFailure) {
			return WebService.prototype.send.apply(
				WebService.AuthenticationService,
				[
					"Login",
					{ userName: userName, password: password, createPersistentCookie: createPersistentCookie },
					onSuccess,
					onFailure
				]
			);
		},
		logout: function(redirectUrl, onSuccess, onFailure) {
			return WebService.prototype.send.apply(
				WebService.AuthenticationService,
				[
					"Logout",
					null,
					onSuccess ? onSuccess.bind(null, [redirectUrl]) : function(redirectUrl) {
						// if no redirectUrl supplied - location.href=self will reload the page (without submitting forms if were)
						location.href = redirectUrl || location.href;
					},
					onFailure
				]
			);
		}
	});
	WebService.AuthenticationService = new WebService.AuthenticationServiceClass();

	WebService.ProfileServiceClass = new Class({
		Extends: WebService,
		url: Config.rootUrl + "Profile_JSON_AppService.axd",
		properties: new Hash(),
		save: function(propertyNamesToSave, onSuccess, onFailure) {
			var propertiesWithValues = {};
			propertyNamesToSave.each(function(name) {
				propertiesWithValues[name] = this.properties[name];
			}, this);
			return WebService.prototype.send.apply(
				WebService.ProfileService,
				[
					"SetPropertiesForCurrentUser",
					{ values: propertiesWithValues, authenticatedUserOnly: false },
					onSuccess,
					onFailure
				]
			);
		},
		load: function(propertyNamesToLoad, onSuccess, onFailure) {
			var methodName, args = new Hash();

			if (!propertyNamesToLoad) {
				methodName = "GetAllPropertiesForCurrentUser";
			} else {
				methodName = "GetPropertiesForCurrentUser";
				propertyNamesToLoad.removeDuplicates();
				args.extend({ properties: propertyNamesToLoad });
			}
			args.extend({ authenticatedUserOnly: false });

			return WebService.prototype.send.apply(
				WebService.ProfileService,
				[
					methodName,
					args,
					function(result) {
						Hash.each(result, function(value, key) {
							this.properties[key] = value;
						}, this);

						if (onSuccess) onSuccess(result);
					} .bind(this),
					onFailure
				]
			);
		}
	});
	WebService.ProfileService = new WebService.ProfileServiceClass();
}, 2);

function addNamespace(ns) {
	if (!ns) return null;
	var levels = ns.split(".");
	var root = window;
	for (var i = 0; i < levels.length; i++) {
		if (root[levels[i]] == undefined) root[levels[i]] = {};
		root = root[levels[i]];
	}
	return root;
}
